﻿using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;

namespace IntoNails.Infrastructure.Security
{
    public enum HashType
    {
        MD5,
        SHA1,
        SHA256,
        SHA384,
        SHA512
    }

    public static class CryptographyHelper
    {
        private static byte[] _protectBuffer;
        private static byte[] _hashBuffer;
        public static SymmetricAlgorithm DefaultSymmetricCryptoServiceProvider { get; set; }
        public static RSACryptoServiceProvider DefaultRsaCryptoServiceProvider { get; set; }

        static CryptographyHelper()
        {
            _protectBuffer = null;
            _hashBuffer = new byte[0];
            DefaultSymmetricCryptoServiceProvider = new AesCryptoServiceProvider();
            DefaultRsaCryptoServiceProvider = new RSACryptoServiceProvider();
            DefaultHashSalt = "";
            HashBufferSize = 65536;
        }

        public static string DefaultProtectSalt
        {
            get { return Encoding.UTF8.GetString(_protectBuffer); }
            set { _protectBuffer = Encoding.UTF8.GetBytes(value); }
        }

        public static string DefaultHashSalt
        {
            get { return Encoding.UTF8.GetString(_hashBuffer); }
            set { _hashBuffer = Encoding.UTF8.GetBytes(value); }
        }

        public static int HashBufferSize { get; set; }

        private static AesCryptoServiceProvider CreateAesCryptoServiceProvider(byte[] key, byte[] iv = null)
        {
            if (key.Length != 32)
            {
                throw new Exception("The key must have 32 bytes!");
            }
            AesCryptoServiceProvider aesCryptoServiceProvider = new AesCryptoServiceProvider();
            aesCryptoServiceProvider.KeySize = 256;
            aesCryptoServiceProvider.Key = key;
            if (iv == null)
            {
                iv = new byte[16];
                Array.Copy(key, iv, 16);
            }
            aesCryptoServiceProvider.IV = iv;
            return aesCryptoServiceProvider;
        }

        public static void Encrypt(this Stream source, Stream destination, SymmetricAlgorithm serviceProvider = null)
        {
            SymmetricAlgorithm symmetricAlgorithm = serviceProvider ?? DefaultSymmetricCryptoServiceProvider;
            CryptoStream cryptoStream = null;
            try
            {
                ICryptoTransform transform = symmetricAlgorithm.CreateEncryptor();
                cryptoStream = new CryptoStream(destination, transform, CryptoStreamMode.Write);
                source.CopyTo(cryptoStream);
            }
            finally
            {
                if (cryptoStream != null)
                {
                    cryptoStream.Close();
                }
            }
        }

        public static void Decrypt(this Stream source, Stream destination, SymmetricAlgorithm serviceProvider = null)
        {
            SymmetricAlgorithm symmetricAlgorithm = serviceProvider ?? DefaultSymmetricCryptoServiceProvider;
            CryptoStream cryptoStream = null;
            try
            {
                ICryptoTransform transform = symmetricAlgorithm.CreateDecryptor();
                cryptoStream = new CryptoStream(source, transform, CryptoStreamMode.Read);
                cryptoStream.CopyTo(destination);
            }
            finally
            {
                if (cryptoStream != null)
                {
                    cryptoStream.Close();
                }
            }
        }

        public static void Encrypt(this Stream source, Stream destination, byte[] key, byte[] iv = null)
        {
            source.Encrypt(destination, CreateAesCryptoServiceProvider(key, iv));
        }

        public static void Decrypt(this Stream source, Stream destination, byte[] key, byte[] iv = null)
        {
            source.Decrypt(destination, CreateAesCryptoServiceProvider(key, iv));
        }

        public static void Encrypt(this Stream source, Stream destination, string key, string iv = null)
        {
            source.Encrypt(destination, Encoding.UTF8.GetBytes(key), (iv != null) ? Encoding.UTF8.GetBytes(iv) : null);
        }

        public static void Decrypt(this Stream source, Stream destination, string key, string iv = null)
        {
            source.Decrypt(destination, Encoding.UTF8.GetBytes(key), (iv != null) ? Encoding.UTF8.GetBytes(iv) : null);
        }

        public static MemoryStream Encrypt(this Stream source, SymmetricAlgorithm serviceProvider = null)
        {
            MemoryStream memoryStream = new MemoryStream();
            source.Encrypt(memoryStream, serviceProvider);
            return memoryStream;
        }

        public static MemoryStream Decrypt(this Stream source, SymmetricAlgorithm serviceProvider = null)
        {
            MemoryStream memoryStream = new MemoryStream();
            source.Decrypt(memoryStream, serviceProvider);
            return memoryStream;
        }

        public static MemoryStream Encrypt(this Stream source, byte[] key, byte[] iv = null)
        {
            return source.Encrypt(CreateAesCryptoServiceProvider(key, iv));
        }

        public static MemoryStream Decrypt(this Stream source, byte[] key, byte[] iv = null)
        {
            return source.Decrypt(CreateAesCryptoServiceProvider(key, iv));
        }

        public static MemoryStream Encrypt(this Stream source, string key, string iv = null)
        {
            return source.Encrypt(Encoding.UTF8.GetBytes(key), (iv != null) ? Encoding.UTF8.GetBytes(iv) : null);
        }

        public static MemoryStream Decrypt(this Stream source, string key, string iv = null)
        {
            return source.Decrypt(Encoding.UTF8.GetBytes(key), (iv != null) ? Encoding.UTF8.GetBytes(iv) : null);
        }

        public static byte[] Encrypt(this byte[] data, SymmetricAlgorithm serviceProvider = null)
        {
            MemoryStream source = new MemoryStream(data);
            MemoryStream memoryStream = new MemoryStream();
            source.Encrypt(memoryStream, serviceProvider);
            return memoryStream.ToArray();
        }

        public static byte[] Decrypt(this byte[] data, SymmetricAlgorithm serviceProvider = null)
        {
            MemoryStream source = new MemoryStream(data);
            MemoryStream memoryStream = new MemoryStream();
            source.Decrypt(memoryStream, serviceProvider);
            return memoryStream.ToArray();
        }

        public static byte[] Encrypt(this byte[] data, byte[] key, byte[] iv = null)
        {
            return data.Encrypt(CreateAesCryptoServiceProvider(key, iv));
        }

        public static byte[] Decrypt(this byte[] data, byte[] key, byte[] iv = null)
        {
            return data.Decrypt(CreateAesCryptoServiceProvider(key, iv));
        }

        public static byte[] Encrypt(this byte[] data, string key, string iv = null)
        {
            return data.Encrypt(Encoding.UTF8.GetBytes(key), (iv != null) ? Encoding.UTF8.GetBytes(iv) : null);
        }

        public static byte[] Decrypt(this byte[] data, string key, string iv = null)
        {
            return data.Decrypt(Encoding.UTF8.GetBytes(key), (iv != null) ? Encoding.UTF8.GetBytes(iv) : null);
        }

        public static string Encrypt(this string data, SymmetricAlgorithm serviceProvider = null)
        {
            byte[] array = Encoding.UTF8.GetBytes(data);
            array = array.Encrypt(serviceProvider);
            return Convert.ToBase64String(array);
        }

        public static string Decrypt(this string data, SymmetricAlgorithm serviceProvider = null)
        {
            byte[] array = Convert.FromBase64String(data);
            array = array.Decrypt(serviceProvider);
            return Encoding.UTF8.GetString(array);
        }

        public static string Encrypt(this string data, byte[] key, byte[] iv = null)
        {
            return data.Encrypt(CreateAesCryptoServiceProvider(key, iv));
        }

        public static string Decrypt(this string data, byte[] key, byte[] iv = null)
        {
            return data.Decrypt(CreateAesCryptoServiceProvider(key, iv));
        }

        public static string Encrypt(this string data, string key, string iv = null)
        {
            return data.Encrypt(Encoding.UTF8.GetBytes(key), (iv != null) ? Encoding.UTF8.GetBytes(iv) : null);
        }

        public static string Decrypt(this string data, string key, string iv = null)
        {
            return data.Decrypt(Encoding.UTF8.GetBytes(key), (iv != null) ? Encoding.UTF8.GetBytes(iv) : null);
        }

        private static RSACryptoServiceProvider CreateRsaCryptoServiceProvider(string xmlRsaKey)
        {
            RSACryptoServiceProvider rsaCryptoServiceProvider = new RSACryptoServiceProvider();
            rsaCryptoServiceProvider.FromXmlString(xmlRsaKey);
            return rsaCryptoServiceProvider;
        }

        public static byte[] RsaEncrypt(this byte[] data, RSACryptoServiceProvider serviceProvider, bool doOAEPPadding = true)
        {
            RSACryptoServiceProvider rsaCryptoServiceProvider = serviceProvider ?? DefaultRsaCryptoServiceProvider;
            return rsaCryptoServiceProvider.Encrypt(data, doOAEPPadding);
        }

        public static byte[] RsaDecrypt(this byte[] data, RSACryptoServiceProvider serviceProvider, bool doOAEPPadding = true)
        {
            RSACryptoServiceProvider rsaCryptoServiceProvider = serviceProvider ?? DefaultRsaCryptoServiceProvider;
            return rsaCryptoServiceProvider.Decrypt(data, doOAEPPadding);
        }

        public static byte[] RsaEncrypt(this byte[] data, bool doOAEPPadding = true)
        {
            return data.RsaEncrypt(DefaultRsaCryptoServiceProvider, doOAEPPadding);
        }

        public static byte[] RsaDecrypt(this byte[] data, bool doOAEPPadding = true)
        {
            return data.RsaDecrypt(DefaultRsaCryptoServiceProvider, doOAEPPadding);
        }

        public static byte[] RsaEncrypt(this byte[] data, string xmlKey, bool doOAEPPadding = true)
        {
            return data.RsaEncrypt(CreateRsaCryptoServiceProvider(xmlKey), doOAEPPadding);
        }

        public static byte[] RsaDecrypt(this byte[] data, string xmlKey, bool doOAEPPadding = true)
        {
            return data.RsaDecrypt(CreateRsaCryptoServiceProvider(xmlKey), doOAEPPadding);
        }

        public static string RsaEncrypt(this string data, RSACryptoServiceProvider serviceProvider, bool doOAEPPadding = true)
        {
            byte[] bytes = Encoding.UTF8.GetBytes(data);
            return Convert.ToBase64String(bytes.RsaEncrypt(serviceProvider, doOAEPPadding));
        }

        public static string RsaDecrypt(this string data, RSACryptoServiceProvider serviceProvider, bool doOAEPPadding = true)
        {
            byte[] data2 = Convert.FromBase64String(data);
            return Encoding.UTF8.GetString(data2.RsaDecrypt(serviceProvider, true));
        }

        public static string RsaEncrypt(this string data, bool doOAEPPadding = true)
        {
            return data.RsaEncrypt(DefaultRsaCryptoServiceProvider, doOAEPPadding);
        }

        public static string RsaDecrypt(this string data, bool doOAEPPadding = true)
        {
            return data.RsaDecrypt(DefaultRsaCryptoServiceProvider, doOAEPPadding);
        }

        public static string RsaEncrypt(this string data, string xmlKey, bool doOAEPPadding = true)
        {
            return data.RsaEncrypt(CreateRsaCryptoServiceProvider(xmlKey), doOAEPPadding);
        }

        public static string RsaDecrypt(this string data, string xmlKey, bool doOAEPPadding = true)
        {
            return data.RsaDecrypt(CreateRsaCryptoServiceProvider(xmlKey), doOAEPPadding);
        }

        public static void RsaEncrypt(this Stream source, Stream destination, RSACryptoServiceProvider serviceProvider, bool doOAEPPadding = true)
        {
            MemoryStream memoryStream = new MemoryStream();
            source.CopyTo(memoryStream);
            byte[] array = memoryStream.ToArray().RsaEncrypt(serviceProvider, doOAEPPadding);
            destination.Write(array, 0, array.Length);
        }

        public static void RsaDecrypt(this Stream source, Stream destination, RSACryptoServiceProvider serviceProvider, bool doOAEPPadding = true)
        {
            MemoryStream memoryStream = new MemoryStream();
            source.CopyTo(memoryStream);
            byte[] array = memoryStream.ToArray().RsaDecrypt(doOAEPPadding);
            destination.Write(array, 0, array.Length);
        }

        public static void RsaEncrypt(this Stream source, Stream destination, bool doOAEPPadding = true)
        {
            source.RsaEncrypt(destination, DefaultRsaCryptoServiceProvider, true);
        }

        public static void RsaDecrypt(this Stream source, Stream destination, bool doOAEPPadding = true)
        {
            source.RsaDecrypt(destination, DefaultRsaCryptoServiceProvider, doOAEPPadding);
        }

        public static void RsaEncrypt(this Stream source, Stream destination, string xmlKey, bool doOAEPPadding = true)
        {
            source.RsaEncrypt(destination, CreateRsaCryptoServiceProvider(xmlKey), doOAEPPadding);
        }

        public static void RsaDecrypt(this Stream source, Stream destination, string xmlKey, bool doOAEPPadding = true)
        {
            source.RsaDecrypt(destination, CreateRsaCryptoServiceProvider(xmlKey), doOAEPPadding);
        }

        public static MemoryStream RsaEncrypt(this Stream source, RSACryptoServiceProvider serviceProvider, bool doOAEPPadding = true)
        {
            MemoryStream memoryStream = new MemoryStream();
            source.RsaEncrypt(memoryStream, doOAEPPadding);
            return memoryStream;
        }

        public static MemoryStream RsaDecrypt(this Stream source, RSACryptoServiceProvider serviceProvider, bool doOAEPPadding = true)
        {
            MemoryStream memoryStream = new MemoryStream();
            source.RsaDecrypt(memoryStream, doOAEPPadding);
            return memoryStream;
        }

        public static MemoryStream RsaEncrypt(this Stream source, bool doOaepPadding = true)
        {
            return source.RsaEncrypt(null as string, doOaepPadding);
        }

        public static MemoryStream RsaDecrypt(this Stream source, bool doOaepPadding = true)
        {
            return source.RsaDecrypt(null as string, doOaepPadding);
        }

        public static MemoryStream RsaEncrypt(this Stream source, string xmlKey, bool doOaepPadding = true)
        {
            return source.RsaEncrypt(CreateRsaCryptoServiceProvider(xmlKey), doOaepPadding);
        }

        public static MemoryStream RsaDecrypt(this Stream source, string xmlKey, bool doOAEPPadding = true)
        {
            return source.RsaDecrypt(CreateRsaCryptoServiceProvider(xmlKey), doOAEPPadding);
        }

        private static byte[] Protect(byte[] data, byte[] salt)
        {
            byte[] optionalEntropy = salt ?? _protectBuffer;
            return ProtectedData.Protect(data, optionalEntropy, DataProtectionScope.CurrentUser);
        }

        private static byte[] Unprotect(byte[] data, byte[] salt)
        {
            byte[] optionalEntropy = salt ?? _protectBuffer;
            return ProtectedData.Unprotect(data, optionalEntropy, DataProtectionScope.CurrentUser);
        }

        public static byte[] Protect(this byte[] data, string salt = null)
        {
            return Protect(data, (salt != null) ? Encoding.UTF8.GetBytes(salt) : null);
        }

        public static string Protect(this string data, string salt = null)
        {
            byte[] inArray = Encoding.UTF8.GetBytes(data).Protect(salt);
            return Convert.ToBase64String(inArray);
        }

        public static byte[] Unprotect(this byte[] data, string salt = null)
        {
            return Unprotect(data, (salt != null) ? Encoding.UTF8.GetBytes(salt) : null);
        }

        public static string Unprotect(this string data, string salt = null)
        {
            byte[] bytes = Convert.FromBase64String(data).Unprotect(salt);
            return Encoding.UTF8.GetString(bytes);
        }

        public static byte[] ComputeHash(Stream data, HashAlgorithm hash, string salt = null)
        {
            byte[] streamBuffer = new byte[HashBufferSize];
            int inputCount;
            while ((inputCount = data.Read(streamBuffer, 0, HashBufferSize)) > 0)
            {
                hash.TransformBlock(streamBuffer, 0, inputCount, streamBuffer, 0);
            }

            byte[] hashBuffer = (salt == null) ? _hashBuffer : Encoding.UTF8.GetBytes(salt);
            hash.TransformFinalBlock(hashBuffer, 0, hashBuffer.Length);

            return hash.Hash;
        }

        public static string ComputeHash(this Stream data, HashType hashType = HashType.MD5, string salt = null)
        {
            HashAlgorithm hash = null;
            switch (hashType)
            {
                case HashType.MD5:
                    hash = new MD5CryptoServiceProvider();
                    break;
                case HashType.SHA1:
                    hash = new SHA1CryptoServiceProvider();
                    break;
                case HashType.SHA256:
                    hash = new SHA256CryptoServiceProvider();
                    break;
                case HashType.SHA384:
                    hash = new SHA384CryptoServiceProvider();
                    break;
                case HashType.SHA512:
                    hash = new SHA512CryptoServiceProvider();
                    break;
            }
            return Convert.ToBase64String(ComputeHash(data, hash, salt));
        }

        public static string ComputeHash(this byte[] data, HashType hashType = HashType.MD5, string salt = null)
        {
            string hash;
            using (MemoryStream mem = new MemoryStream(data))
            {
                hash = mem.ComputeHash(hashType, salt);
            }

            return hash;
        }

        public static string ComputeHash(this string data, HashType hashType = HashType.MD5, string salt = null)
        {
            string hash;
            using (MemoryStream mem = new MemoryStream(Encoding.UTF8.GetBytes(data)))
            {
                hash = mem.ComputeHash(hashType, salt);
            }

            return hash;
        }
    }
}
